#include "CTreasurePool.h"
#include "structs.h"
#include "CParty.h"
#include "define.h"
#include "CFindItemPacket.h"
#include "CLotItemPacket.h"
#include "vector"
#include "CStandardMessagePacket.h"

CTreasurePool::CTreasurePool() {
	for(unsigned char slotID = 0; slotID < 10; slotID++) {
		m_poolItems[slotID].itemID = 0;
		m_poolItems[slotID].itemQuantity = 0;
		m_poolItems[slotID].timestamp = 0;
		m_poolItems[slotID].itemLotters.clear();
	}
	m_lastCheckTime = 0;
	setPoolType(TREASUREPOOL_SOLO);
	m_pTreasurePoolSemaphore=new CSemaphore(1);
	unlockTreasurePool();
}

CTreasurePool::~CTreasurePool() {
	for(unsigned char slotID = 0; slotID < 10; slotID++) {
		m_poolItems[slotID].itemLotters.clear();
	}
	lockTreasurePool();
	delete m_pTreasurePoolSemaphore;
}

void CTreasurePool::addItem(unsigned short itemID, unsigned int quantity, CBaseEntity * pEntity) {
	
	//Lock the treasure pool before we get started
	this->lockTreasurePool();
	//Verify there is someone in the treasure pool
	if (!m_members.empty()) {
		//Find a free slot in the treasure pool to hold this item
		unsigned char freeSlot = -1;
		unsigned int oldest = -1;
		unsigned char slotID;
		for(slotID = 0; slotID < 10; slotID++) {
			//Check if the slot is free
			if ( (m_poolItems[slotID].itemID == 0) && (m_poolItems[slotID].itemQuantity == 0) ) {
				//Slot in the item pool is free; jump out and use this slot ID
				freeSlot = slotID;
				break;
			} else {
				//Slot is not free; but it may be the oldest item so we may need to push it out anyways
				if (m_poolItems[slotID].timestamp < oldest) {
					oldest = m_poolItems[slotID].timestamp;
					freeSlot = slotID;
				}
			}
		}
		//If there are no free slots, make one
		if (slotID == 10) {
			//No free slots were found; remove the oldest item for a newer one
			m_poolItems[freeSlot].timestamp = 0; //force a timeout win
			//Unlock the treasure pool quickly so we can clear out an item
			this->unlockTreasurePool();
			//Clear out the oldest item
			this->checkItems();
			//Lock the treasure pool so we can continue
			this->lockTreasurePool();
		}

		//If there is only 1 person in the treasure pool
		if (m_members.size() == 1) {
			//Gives this person the item if they have it
			if ( m_members.at(0)->addItem(itemID,LOC_INVENTORY,quantity) != 0xFF ) {
				//Update the inventory
				m_members.at(0)->sendFinishInvStor();
				//Successfully added the item to the users inventory
				//Found item on the monster
				this->sendToMembers(CFindItemPacket(itemID, quantity, 0, freeSlot, pEntity));
				//Player wins the item
				this->sendToMembers(CLotItemPacket(m_members.at(0), freeSlot, (unsigned short)-1, (unsigned char)ITEMLOT_WIN));
				//unlock
				this->unlockTreasurePool();
				return;
			}
		//Check to see if this is a party pool, and not a zone-wide pool
		} else if ((m_members.at(0)->getParty() != NULL) && (this->getPoolType() != TREASUREPOOL_ZONE)) {
			// Check to see if there is a quarter master for this party
			if (m_members.at(0)->getParty()->getQM() != NULL) {
				//There is a quarter master, check to see if the QM is in the same zone
				if (m_members.at(0)->getZone() == m_members.at(0)->getParty()->getQM()->getZone()){
					//The QM is in the same zone
					if ( m_members.at(0)->getParty()->getQM()->addItem(itemID,LOC_INVENTORY,quantity) != 0xFF ) {
						//Update the inventory
						m_members.at(0)->getParty()->getQM()->sendFinishInvStor();
						//Successfully added the item to the QM's inventory
						//Found item on the monster
						this->sendToMembers(CFindItemPacket(itemID, quantity, 0, freeSlot, pEntity));
						//QM wins the item
						this->sendToMembers(CLotItemPacket(m_members.at(0)->getParty()->getQM(), freeSlot, (unsigned short)-1, (unsigned char)ITEMLOT_WIN));
						//Unlock
						this->unlockTreasurePool();
						return;
					} else {
						//Tell the QM that he is full
						m_members.at(0)->getParty()->getQM()->quePacket(CStandardMessagePacket(QM_FULL,itemID));
					}
				}
			}
		}
		
		//Add the item to the pool
		//Generate the timestamp for the item
		time_t rawtime;
		time(&rawtime);

		//Puts the item in the pool array
		m_poolItems[freeSlot].timestamp = (unsigned int)rawtime;
		m_poolItems[freeSlot].itemQuantity = quantity;
		m_poolItems[freeSlot].itemID = itemID;
		//Adds the new item
		if (!m_members.empty()) {
			//There is a member list
			for (unsigned int x = 0; x < m_members.size(); x++) {
				//Verify that this member is not a null pointer
				if (m_members.at(x) != NULL) {
					struct lotInfo lotter;
					lotter.member = m_members.at(x);
					lotter.lot = 0;
					//Push the potential lotter onto the list
					m_poolItems[freeSlot].itemLotters.push_back(lotter);
				}
			}
		}
		this->sendToMembers(CFindItemPacket(itemID, quantity, m_poolItems[freeSlot].timestamp, freeSlot, pEntity));
	}
	this->unlockTreasurePool();
}

bool CTreasurePool::hasTreasure(void) {
	this->lockTreasurePool();
	for(unsigned char slotID = 0; slotID < 10; slotID++) {
		//Check if the slot is free
		if ( (m_poolItems[slotID].itemID != 0) && (m_poolItems[slotID].itemQuantity != 0) ) {
			//There is treasure in the pool
			this->unlockTreasurePool();
			return true;
		}
	}	
	return false;
	this->unlockTreasurePool();
}

void CTreasurePool::lotItem(CChar * lotter, unsigned char slotID, unsigned short lot) {
	//Verify that the lot is on a valid item (< slot # 10)
	if (slotID < 10) {
		this->lockTreasurePool();
		//Make sure there is an item ID and quantity for the item that is being lotted on
		if ((m_poolItems[slotID].itemID != 0) && (m_poolItems[slotID].itemQuantity != 0)) {
			//Make sure there are people that can lot on the item
			if (!m_poolItems[slotID].itemLotters.empty()) {
				// Loop through all of the people that can lot on this item to find the lotter
				for (unsigned int x = 0; x < m_poolItems[slotID].itemLotters.size(); x++) {
					//Verify that the member we are checking is not null
					if (m_poolItems[slotID].itemLotters.at(x).member != NULL) {
						//Check to see if this person is the same person as the lotter
						if (m_poolItems[slotID].itemLotters.at(x).member->getId() == lotter->getId()) {
							////////Randomly generate a lot
							//unsigned short lot = 1+(unsigned short)(rand()%1000);
							if ((m_poolItems[slotID].itemLotters.at(x).lot == 0) || (lot == 0xFFFF)) {
								m_poolItems[slotID].itemLotters.at(x).lot = lot;
								//Now that we have lotted, check to see if this item is won
								if (this->checkItem(slotID) == 1) {
									//The item is won; find the winner
									struct lotInfo winner = this->getWinner(slotID);
									if (winner.member != NULL) {
										//Check to see if the winner can hold the item
										if (winner.member->addItem(m_poolItems[slotID].itemID,LOC_INVENTORY,m_poolItems[slotID].itemQuantity) != 0xFF) {
											//Winner wins the item
											this->sendToMembers(CLotItemPacket(lotter,winner.member, slotID, lot, winner.lot, (unsigned char)ITEMLOT_WIN));
											//Update the inventory
											winner.member->sendFinishInvStor();
										} else {
											//Winner wins the item; but cannot hold it so the item is lost
											this->sendToMembers(CLotItemPacket(lotter,winner.member, slotID, lot, winner.lot, (unsigned char)ITEMLOT_WINERROR));
										}
									} else {
										//No one wanted the item, remove it
										this->sendToMembers(CLotItemPacket(slotID,(unsigned char)ITEMLOT_LOST));
									}
									// Clear the item out of the pool
									m_poolItems[slotID].itemID = 0;
									m_poolItems[slotID].itemLotters.clear();
									m_poolItems[slotID].timestamp = 0;
									m_poolItems[slotID].itemQuantity = 0;
								} else {
									// Tell everyone what the successful lot was
									this->sendToMembers(CLotItemPacket(lotter, slotID, lot,(bool)true));
								}
								break;
							}
						}
					}
				}
			}
		}
		this->unlockTreasurePool();
	}
}


unsigned short CTreasurePool::getPoolType() {
	return m_poolType;
}

void CTreasurePool::setPoolType(unsigned short poolType) {
	m_poolType = poolType;
}

unsigned int CTreasurePool::getLastCheckTime() {
	return m_lastCheckTime;
}

void CTreasurePool::setLastCheckTime(unsigned int lastCheckTime) {
	m_lastCheckTime = lastCheckTime;
}

//Checks all of the items on a timer interval and when someone leaves the treasure pool
void CTreasurePool::checkItems() {
	this->lockTreasurePool();
	//Check all of the items
	for (unsigned int x = 0; x < 10; x++) {
		//Check only the items that have an itemID and an item quantity
		if ((m_poolItems[x].itemID != 0) && (m_poolItems[x].itemQuantity != 0)) {
			// See if this item is ready to be won
			if (this->checkItem(x) == 1) {
				//Item has been won
				struct lotInfo winner = this->getWinner(x);
				if (winner.member != NULL) {
					// Give the item to the player
					if (winner.member->addItem(m_poolItems[x].itemID,LOC_INVENTORY,m_poolItems[x].itemQuantity) != 0xFF) {
						// Display the win message
						this->sendToMembers(CLotItemPacket(winner.member,(unsigned char)x,winner.lot, (unsigned char)ITEMLOT_WIN));
						//Update the inventory
						winner.member->sendFinishInvStor();
					} else {
						// Display win, but item could not be obtained (full inventory)
						//this->sendToMembers(CLotItemPacket(winner.member,(unsigned char)x,winner.lot, (unsigned char)ITEMLOT_WINERROR));
						this->sendToMembers(CStandardMessagePacket(ITEM_LOST,this->m_poolItems[x].itemID));
						this->sendToMembers(CLotItemPacket((unsigned char)x,(unsigned char)ITEMLOT_LOST));
					}
				} else {
					//No one wanted the item, so it was lost
					this->sendToMembers(CLotItemPacket((unsigned char)x,(unsigned char)ITEMLOT_LOST));
				}
				// Clear the item out of the pool
				m_poolItems[x].itemID = 0;
				m_poolItems[x].itemLotters.clear();
				m_poolItems[x].timestamp = 0;
				m_poolItems[x].itemQuantity = 0;
			}
		}
	}
	this->unlockTreasurePool();
}

//This function will check a single slot to see if its time to give the item to a winner
int CTreasurePool::checkItem(unsigned char slotID) {
	if ((m_poolItems[slotID].itemID != 0) && (m_poolItems[slotID].itemQuantity != 0)) {	
		time_t currentTime;
		time(&currentTime);
		//Check to see if the item timestamp has expired
		if ((unsigned int)(m_poolItems[slotID].timestamp + 300) < (unsigned int)currentTime) {
			return 1;
		} else {
			//Check to see if everyone has lotted on the item

			//Paranoid safety check (yes, i am that crazy)
			if (!m_poolItems[slotID].itemLotters.empty()) {
				if (m_poolItems[slotID].itemLotters.size() > 1) {
					unsigned int result = 1;
					for (unsigned int y = 0; y < m_poolItems[slotID].itemLotters.size(); y++) {
						//Check to see if this person has not lotted yet
						if ((m_poolItems[slotID].itemLotters.at(y).lot == 0) && (m_poolItems[slotID].itemLotters.at(y).member != NULL)) {
							//This person has yet to vote, we may be able to jump out quickly
							result = 0;
							//If the person is not in a party or if its a zone-wide pool then we can jump out earily
							if ((m_poolItems[slotID].itemLotters.at(y).member->getParty() == NULL) || (m_poolType == TREASUREPOOL_ZONE)) {
								break;
							}
							//If your in a party; you may be a QM
						} else if ( (m_poolItems[slotID].itemLotters.at(y).member->getParty() != NULL) && (m_poolType != TREASUREPOOL_ZONE) ) {
							//See if this person is a quarter master
							if (m_poolItems[slotID].itemLotters.at(y).member->getParty()->getQM() == m_poolItems[slotID].itemLotters.at(y).member) {
								if ((m_poolItems[slotID].itemLotters.at(y).member->getFreeSlotCount(LOC_INVENTORY) > 0) && (m_poolItems[slotID].itemLotters.at(y).lot != 0xFFFF) ) {
									//They are in the same zone, give it to the QM
									result = 1;
									break;
								}
							}

						}
					}
					return result;
					
					//Single user check
				} else if ((m_poolItems[slotID].itemLotters.size() == 1) && ((m_poolItems[slotID].itemLotters.at(0).member->getFreeSlotCount(LOC_INVENTORY) > 0) || (m_poolItems[slotID].itemLotters.at(0).lot == 0xFFFF)) ) {
					return 1;
				}
			}
		}
	}
	//This item has not been won yet
	return 0;
}
//Add member adds a new player to the treasure pool
void CTreasurePool::addMember(CChar * member) {
	this->lockTreasurePool();

	//Check to see if the player already exists
	if (!m_members.empty()) {
		for (unsigned int x = 0; x < m_members.size(); x++) {
			if (m_members.at(x) != NULL) {
				if (m_members.at(x)->getId() == member->getId()) {
					this->unlockTreasurePool();
					return;
				}
			}
		}
	}
	//Add the new member to the members list
	m_members.push_back(member);

	//Define the new lotter to add
	struct lotInfo lotter;
	lotter.member = member;
	lotter.lot = 0;

	//Add the new person to the treasure pool so he can lot
	for (unsigned int x = 0; x < 10; x++) {
		if ((m_poolItems[x].itemID != 0) && (m_poolItems[x].itemQuantity != 0)) {
			//Send the find item packet to populate the treasure pool
			member->quePacket(CFindItemPacket(m_poolItems[x].itemID, m_poolItems[x].itemQuantity, this->m_poolItems[x].timestamp, (unsigned char)x));
			//Are there even people able to lot on this item? (paranoid saftey check)
			if (!this->m_poolItems[x].itemLotters.empty()) {
				for (unsigned int y = 0; y < this->m_poolItems[x].itemLotters.size(); y++) {
					if ((this->m_poolItems[x].itemLotters[y].lot != 0) && (this->m_poolItems[x].itemLotters[y].member != NULL)) {
						//Send the lot result packet because this person would not have the information (but don't announce the name because its an old lot)
						member->quePacket(CLotItemPacket(this->m_poolItems[x].itemLotters[y].member,x,this->m_poolItems[x].itemLotters[y].lot, false));
					}
				}
			}
			this->m_poolItems[x].itemLotters.push_back(lotter);
		} else {
			//Clears the slot incase the client has something here
			member->quePacket(CFindItemPacket((unsigned char)x));
		}
	}
	this->unlockTreasurePool();
}
	
//Remove member removes an exisiting member from the treasure pool
void CTreasurePool::removeMember(CChar * member) {
	this->lockTreasurePool();

	//Check for the player on the global list
	for (unsigned int x = 0; x < m_members.size(); x++) {
		if (m_members.at(x) == member) {
			m_members.erase(m_members.begin()+x,m_members.begin()+x+1);
			break;
		}
	}
	//If your in a zone-wide treasurepool you don't get to re-lot; so no reason to remove you
	if (this->getPoolType() != TREASUREPOOL_ZONE) {
		//Remove the person from all of the items in the treasure pool
		for (unsigned int x = 0; x < 10; x++) {
			if ((this->m_poolItems[x].itemID != 0) && (this->m_poolItems[x].itemQuantity != 0)) {
				for (unsigned int y = 0; y < m_poolItems[x].itemLotters.size(); y++) {
					if (m_poolItems[x].itemLotters.at(y).member == member) {
						m_poolItems[x].itemLotters.erase(m_poolItems[x].itemLotters.begin()+y,m_poolItems[x].itemLotters.begin()+y+1);
						break;
					}
				}
			}
		}
	}
	unlockTreasurePool();
	
	//Clears the leavers treasure pool
	member->clearTreasurePool();
	//un-sets the treasure pool
	member->setTreasurePool(NULL);
	//See if we removed everyone now
	if (m_members.empty()) {
		//Everyone has left, delete the treasure pool
		delete this;
	} else {
		//Check to see if someone will win the items now that a member has been removed.
		checkItems();
	}
}

//Sends a treasure pool update to everyone
int CTreasurePool::sendToMembers(CBasicPacket pack) {
	for (unsigned int x = 0; x < m_members.size(); x++) {
		if (m_members.at(x) != NULL) {
			m_members.at(x)->quePacket(pack);
		}
	}
	return 0;
}

//Forces a winner to be picked, will randomly choose if nessessary
struct CTreasurePool::lotInfo CTreasurePool::getWinner(unsigned char slotID) {

	// Winner that we will figure out for the Return value
	struct lotInfo winner;
	// Determines if we need to insert or push_back into the vector
	bool inserted;

	//Incase people have the same lot and highLot may not win, what is the value for randomization purposes
	int winnerLot = 0;

	//Number of people with the same lot
	unsigned int sameLot = 0;

	//Initilize winners vector
	std::vector<struct lotInfo*> winners;
	winners.clear();
	
	//Check all of the voters for this item
	for (unsigned int x = 0; x < this->m_poolItems[slotID].itemLotters.size(); x++) {
		//Make sure the voter is not a null pointer
		if (this->m_poolItems[slotID].itemLotters.at(x).member != NULL) {
			if (this->m_poolItems[slotID].itemLotters.at(x).lot != 0xFFFF) {

				if ((this->m_poolItems[slotID].itemLotters.at(x).lot > winnerLot) && (this->m_poolItems[slotID].itemLotters.at(x).member->getFreeSlotCount(LOC_INVENTORY))) {
					winnerLot = this->m_poolItems[slotID].itemLotters.at(x).lot;
					sameLot = 1;
				} else if (this->m_poolItems[slotID].itemLotters.at(x).lot == winnerLot) {
					if (this->m_poolItems[slotID].itemLotters.at(x).member->getFreeSlotCount(LOC_INVENTORY)) {
						sameLot++;
					} else { // eww hack -.-
						this->m_poolItems[slotID].itemLotters.at(x).lot--;
					}
				}
				//Find a location to add this new entry
				inserted = false;
				for (unsigned int y = 0; y < winners.size(); y++) {
					if ((short)winners.at(y)->lot < (short)(this->m_poolItems[slotID].itemLotters.at(x).lot)) {
						winners.insert(winners.begin()+y,&this->m_poolItems[slotID].itemLotters.at(x));
						inserted = true;
						break;
					}
				}
				if (!inserted) {
					winners.push_back(&this->m_poolItems[slotID].itemLotters.at(x));
				}
			}
		}
	}
	

	//Make sure there is a winner
	if (!winners.empty()) {

		int winnerIndex = 0;

		for (unsigned int x = 0; x < winners.size(); x++) {
			if (winners.at(x)->lot == winnerLot) {
				//Randomly generate a number for the people that have the same lot
				//	it will be very uncommon for this to occur; but its better to plan for the worst
				if (sameLot == 0) 
					sameLot = 1;
				winnerIndex = x+(unsigned int)(rand()%(sameLot));
				break;
			} else if ((short)winners.at(x)->lot > (short)winnerLot) {
				//Generate the message you cannot obtain this item if you had a higher lot than the winner
				winners.at(x)->member->quePacket(CStandardMessagePacket(ITEM_LOST,this->m_poolItems[slotID].itemID));

			}
		}

		winner.member = winners.at(winnerIndex)->member;
		winner.lot = winners.at(winnerIndex)->lot;
		//Return the winner to the calling function
	} else {
		//No one wanted the item
		//Default to NULL and pass lot
		winner.member = NULL;
		winner.lot = -1;
		//Return that no one won
	}
	return winner;
}


bool CTreasurePool::lockTreasurePool(void)
{
	return m_pTreasurePoolSemaphore->lock();
}

bool CTreasurePool::unlockTreasurePool(void)
{
	return m_pTreasurePoolSemaphore->unlock();
}